Flutter RenderAndroidView
在 Virtual displays 模式下,在 Flutter 侧,开发者直接打交道的组件是 AndroidView。AndroidView 中通过层层封装,最终负责展示的组件 RenderAndroidView,它是一个 RenderObject,负责实际的嵌入 Android 视图展示。在本文中,我们将介绍 RenderAndroidView 的实现,了解了它,就了解了 Virtual displays 在 Flutter 侧和核心原理。上层的类,只是进行一些封装和通信功能。
paint 绘制
RenderAndroidView,继承自 RenderBox,是一个 RenderObject。RenderObject 的 paint 负责实际绘制。RenderAndroidView 的绘制方法如下:
@override
void paint(PaintingContext context, Offset offset) {
// 必须要有纹理 id
if (_viewController.textureId == null) return;
// ...
_paintTexture(context, offset);
}
其中,_viewController
我们在前面组成结构中提到过,你看都一层层传递到最底层了。它其中持有一个 id,这是原生视图的纹理 id。纹理 id 是什么呢?在本文中我们不深究它,简单理解为,只要原生侧那边工作正常,就会产生这个 id,而 Flutter 这边,只要拿到这个 id,即可进行后续绘制工作。(纹理 id 将在《Flutter Android Virtual Display 实现原理》的其他文章中介绍)。
_paintTexture
纹理 id 可以不讲,RenderAndroidView 怎么拿纹理 id 进行绘制的,这个还是要讲的。继续看 _paintTexture
:
void _paintTexture(PaintingContext context, Offset offset) {
context.addLayer(TextureLayer(
rect: offset & _currentAndroidViewSize,
textureId: _viewController.textureId!,
freeze: _state == _PlatformViewState.resizing,
));
}
首先,看到了纹理 id,它作为 TextureLayer 的构造函数,TextureLayer 是什么呢?文档中说到,这是一个将将后端纹理映射到矩形的合成层。也可以点击《Flutter TextureLayer》阅读我的笔记。
不难看出,我们将 textureId 对应的纹理,映射到 rect 对应的区域。rect 里面怎么还个 &
运算?这是 Offset 类重载的运算符,用于将 Rect 偏移 Offset 的距离。
还有个 freeze 是干什么的?一个布尔值,表示是否冻结纹理。冻结后纹理不再更新。不难看出 _state
是一个状态机,冻结条件是 RenderAndroidView 处于 resizing(缩放)状态,可以理解为在缩放过程中,纹理不更新,等待缩放过程完成后,再更新纹理,避免在视图大小改变时出现不必要的缩放效果。
下面再看 PaintingContext,PaintingContext 是 Flutter 中的一个关键类,用于管理和绘制图形。它作为绘图操作的上下文,提供了一系列方法来绘制在屏幕上显示的图形。
addLayer
方法用于在绘图过程中添加图层。图层是独立于主画布的绘制环境,可以用于实现各种视觉效果。如此看来,渲染纹理就要通过添加一个 TextureLayer 的层来实现。
3 个 override get
在 RenderAndroidView 中,有 3 个 override 的 get 属性:
@override
bool get sizedByParent => true;
@override
Size computeDryLayout(BoxConstraints constraints) {
return constraints.biggest;
}
@override
bool get alwaysNeedsCompositing => true;
@override
bool get isRepaintBoundary => true;
根据文档他们的含义分别是:
sizedByParent
:true,RenderAndroidView 大小由父组件决定,结合 computeDryLayout 可知,会尽可能充满父容器的空间alwaysNeedsCompositing
:true,用 PaintingContext 添加新图层后,需要将其设置为 trueisRepaintBoundary
:true,RenderObject 与父对象分开重新绘制。
performResize
Resize(缩放)是 RenderAndroidView 中的一项核心操作,从前面我们看到 RenderAndroidView 是个状态机,专门有一个状态叫 _PlatformViewState.resizing
就能看出,事情不简单。
@override
void performResize() {
super.performResize();
_sizePlatformView();
}
这段代码调用了 _sizePlatformView
,注意这是一个异步方法,它用于调整平台视图的大小。
// 这个属性存储了 Android 原生侧视图的大小
late Size _currentAndroidViewSize;
// 注意这是一个异步方法
Future<void> _sizePlatformView() async {
// ...
// 进入 resizing 缩放中状态
_state = _PlatformViewState.resizing;
markNeedsPaint();
Size targetSize;
do {
targetSize = size;
// 调整原生视图大小,是个异步耗时操作
await _viewController.setSize(targetSize);
// 更新 Android 视图大小
_currentAndroidViewSize = targetSize;
// We've resized the platform view to targetSize, but it is possible that
// while we were resizing the render object's size was changed again.
// In that case we will resize the platform view again.
} while (size != targetSize);
_state = _PlatformViewState.ready;
markNeedsPaint();
}
方法将状态设置为_PlatformViewState.resizing
,并标记需要重绘。这是为了告诉Flutter框架,视图的大小正在改变,需要重新绘制视图。
其中包含一个 do...while
,这是因为 _viewController.setSize
是一个异步耗时操作,而 Flutter 侧的 AndroidView 视图大小可能处于变化当中。这时,尽管 _viewController.setSize
设置完了,但是 AndroidView 因为处于变化中,所以大小又变了。
于是,设计一个 do...while
环节,让原生视图的大小不断追齐 AndroidView 大小。什么时候停止呢?直到 AndroidView 不动了。举一个例子,假设开发者给 AndroidView 添加了一个缩放动画,在动画执行过程中,上面的 do...while
便不断调整大小,以匹配。直到动画执行结束,AndroidView 大小不再变化了,此时也能从这个循环里出来了。
之后,看到将状态切换为了 _PlatformViewState.ready
状态。
在 _paintTexture
的 TextureLayer 创建时,有一个 freeze 属性,与 _PlatformViewState.resizing
状态是关联的,让我们结合缩放过程再来理解一下:
在 do...while
前有一个 markNeedsPaint();
标记需要绘制,然后便进入了 do...while
。假设 resizing 过程特别长,AndroidView 视图大小连续变了假设 10s,这 10s 都落入 do...while
你追我赶。但是 Flutter 的帧调度是正常调度的,在 _sizePlatformView
await 释放线程占用后,会进入帧调度,它知道 AndroidView 标记了 NeedsPaint,于是调用 paint,而这时提交的 TextureLayer 里面,只有 rect 变了,由于是 freeze,纹理是不更新的。这说明了什么?假设这个过程中,原生视图是个播放中的播放器,在这个缩放过程中,播放器的视图大小会同步进行拉伸、缩放,但是播放器的内容是不更新的,因为是 freeze。
注:上面这段内容,是我根据代码静态分析后给出的推论,因此可能并不正确。如果你对此有深入了解,欢迎批评指正,谢谢。
本文作者:Maeiee
本文链接:Flutter RenderAndroidView
版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!
喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!